package top.cardone.security.shiro.web.filter.authc.impl;

import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.SavedRequest;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.CodeExceptionUtils;
import top.cardone.core.CodeException;
import top.cardone.security.shiro.authc.impl.UsernamePasswordTokenImpl;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;

/**
 * Created by yht on 16-2-2.
 */
@Log4j2
public class FormAuthenticationFilterImpl extends FormAuthenticationFilter {
	@Setter
	private String validationCodeParam = "KAPTCHA_SESSION_KEY";

	@Setter
	private String validationParam = "validation-login";

	/**
	 * 单个对象键名
	 */
	@Setter
	private String objectKey = "data";

	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
	                                 ServletRequest request, ServletResponse response) throws Exception {
		if (StringUtils.isNotBlank(validationParam)) {
			subject.getSession().removeAttribute(this.validationParam);
		}

		if (!StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
			return super.onLoginSuccess(token, subject, request, response);
		}

		String successUrl = StringUtils.EMPTY;

		SavedRequest savedRequest = org.apache.shiro.web.util.WebUtils.getAndClearSavedRequest(request);

		if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
			String requestURI = getPathWithinApplication(request);

			if (!StringUtils.equals(savedRequest.getRequestUrl(), requestURI)) {
				successUrl = savedRequest.getRequestUrl();
			}
		} else {
			successUrl = this.getSuccessUrl();
		}

		response.setCharacterEncoding(CharEncoding.UTF_8);
		response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);

		try (Writer out = response.getWriter()) {
			Map<String, Object> successMap = Maps.newHashMap();

			successMap.put(objectKey, successUrl);

			String json = ApplicationContextHolder.getBean(Gson.class).toJson(successMap);

			out.write(json);

			out.flush();
		} catch (java.io.IOException ex) {
			log.error(ex.getMessage(), ex);
		}

		return false;
	}

	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
	                                 ServletRequest request, ServletResponse response) {
		if (StringUtils.isNotBlank(validationParam)) {
			SecurityUtils.getSubject().getSession().setAttribute(validationParam, true);
		}

		if (!StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
			return super.onLoginFailure(token, e, request, response);
		}

		response.setCharacterEncoding(CharEncoding.UTF_8);
		response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);

		Map<String, String> errorInfo;

		try (Writer out = response.getWriter()) {
			String requestURI = getPathWithinApplication(request);

			if (e instanceof IncorrectCredentialsException) {
				errorInfo = CodeExceptionUtils.newMap(requestURI, "000004", "用户名密码不正确");
			} else {
				errorInfo = CodeExceptionUtils.newMap(requestURI, e);
			}

			String json = ApplicationContextHolder.getBean(Gson.class).toJson(errorInfo);

			out.write(json);
		} catch (java.io.IOException ex) {
			log.error(ex.getMessage(), ex);
		}

		return false;
	}

	private Map<String, Object> getByJson(ServletRequest request) {
		String jsonString;

		try (java.io.InputStream is = request.getInputStream()) {
			jsonString = IOUtils.toString(is, request.getCharacterEncoding());
		} catch (IOException e) {
			throw new CodeException("000012", new String[]{e.getMessage()}, e);
		}

		return ApplicationContextHolder.getBean(Gson.class).fromJson(jsonString, Map.class);
	}

	private Map<String, Object> getByParameter(ServletRequest request) {
		Map<String, Object> parameterMap = org.springframework.web.util.WebUtils.getParametersStartingWith(request, null);

		return parameterMap;
	}

	protected AuthenticationToken createToken(String username, String password,
	                                          ServletRequest request, ServletResponse response) {
		Boolean validation = true;

		if (StringUtils.isNotBlank(validationParam)) {
			validation = BooleanUtils.isTrue((Boolean) SecurityUtils.getSubject().getSession().getAttribute(validation));
		}

		UsernamePasswordTokenImpl usernamePasswordToken = new UsernamePasswordTokenImpl();

		Map<String, Object> parameterMap;

		if (StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
			parameterMap = this.getByJson(request);
		} else {
			parameterMap = this.getByParameter(request);
		}

		String validationCode = null;

		String serviceValidationCode = null;

		if (StringUtils.isNotBlank(this.validationCodeParam) && validation) {
			validationCode = MapUtils.getString(parameterMap, validationCodeParam);

			serviceValidationCode = (String) SecurityUtils.getSubject().getSession().getAttribute(this.validationCodeParam);

			SecurityUtils.getSubject().getSession().removeAttribute(this.validationCodeParam);
		}

		username = MapUtils.getString(parameterMap, this.getUsernameParam());

		password = MapUtils.getString(parameterMap, this.getPasswordParam());

		boolean rememberMe = BooleanUtils.toBoolean(MapUtils.getString(parameterMap, this.getRememberMeParam()));

		String host = getHost(request);

		usernamePasswordToken.setUsername(username);
		usernamePasswordToken.setPassword(password != null ? password.toCharArray() : null);
		usernamePasswordToken.setValidation(validation);
		usernamePasswordToken.setValidationCode(validationCode);
		usernamePasswordToken.setServiceValidationCode(serviceValidationCode);
		usernamePasswordToken.setHost(host);
		usernamePasswordToken.setRememberMe(rememberMe);

		return usernamePasswordToken;
	}
}
